/*
 * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.oa.poa;

import java.util.Set;

import org.omg.CORBA.SystemException;

import org.omg.PortableServer.ServantActivator;
import org.omg.PortableServer.Servant;
import org.omg.PortableServer.ServantManager;
import org.omg.PortableServer.ForwardRequest;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.ServantNotActive;
import org.omg.PortableServer.POAPackage.ObjectAlreadyActive;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive;
import org.omg.PortableServer.POAPackage.NoServant;

import com.sun.corba.se.impl.orbutil.concurrent.SyncUtil;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.orbutil.ORBConstants;

import com.sun.corba.se.impl.oa.NullServantImpl;

import com.sun.corba.se.impl.javax.rmi.CORBA.Util;

import com.sun.corba.se.spi.oa.OAInvocationInfo;
import com.sun.corba.se.spi.oa.NullServant;

/**
 * Implementation of POARequesHandler that provides policy specific
 * operations on the POA.
 */
public class POAPolicyMediatorImpl_R_USM extends POAPolicyMediatorBase_R {
    protected ServantActivator activator;

    POAPolicyMediatorImpl_R_USM(Policies policies, POAImpl poa) {
        // assert policies.retainServants()
        super(policies, poa);
        activator = null;

        if (!policies.useServantManager())
            throw poa.invocationWrapper().policyMediatorBadPolicyInFactory();
    }

    /* This handles a rather subtle bug (4939892).  The problem is that
     * enter will wait on the entry if it is being etherealized.  When the
     * deferred state transition completes, the entry is no longer in the
     * AOM, and so we need to get a new entry, otherwise activator.incarnate
     * will be called twice, once for the old entry, and again when a new
     * entry is created.  This fix also required extending the FSM StateEngine
     * to allow actions to throw exceptions, and adding a new state in the
     * AOMEntry FSM to detect this condition.
     */
    private AOMEntry enterEntry(ActiveObjectMap.Key key) {
        AOMEntry result = null;
        boolean failed;
        do {
            failed = false;
            result = activeObjectMap.get(key);

            try {
                result.enter();
            } catch (Exception exc) {
                failed = true;
            }
        } while (failed);

        return result;
    }

    protected java.lang.Object internalGetServant(byte[] id,
                                                  String operation) throws ForwardRequest {
        if (poa.getDebug()) {
            ORBUtility.dprint(this,
                    "Calling POAPolicyMediatorImpl_R_USM.internalGetServant " +
                            "for poa " + poa + " operation=" + operation);
        }

        try {
            ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
            AOMEntry entry = enterEntry(key);
            java.lang.Object servant = activeObjectMap.getServant(entry);
            if (servant != null) {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: servant already activated");
                }

                return servant;
            }

            if (activator == null) {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: no servant activator in POA");
                }

                entry.incarnateFailure();
                throw poa.invocationWrapper().poaNoServantManager();
            }

            // Drop the POA lock during the incarnate call and
            // re-acquire it afterwards.  The entry state machine
            // prevents more than one thread from executing the
            // incarnate method at a time within the same POA.
            try {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: upcall to incarnate");
                }

                poa.unlock();

                servant = activator.incarnate(id, poa);

                if (servant == null)
                    servant = new NullServantImpl(
                            poa.omgInvocationWrapper().nullServantReturned());
            } catch (ForwardRequest freq) {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: incarnate threw ForwardRequest");
                }

                throw freq;
            } catch (SystemException exc) {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: incarnate threw SystemException " + exc);
                }

                throw exc;
            } catch (Throwable exc) {
                if (poa.getDebug()) {
                    ORBUtility.dprint(this,
                            "internalGetServant: incarnate threw Throwable " + exc);
                }

                throw poa.invocationWrapper().poaServantActivatorLookupFailed(
                        exc);
            } finally {
                poa.lock();

                // servant == null means incarnate threw an exception,
                // while servant instanceof NullServant means incarnate returned a
                // null servant.  Either case is an incarnate failure to the
                // entry state machine.
                if ((servant == null) || (servant instanceof NullServant)) {
                    if (poa.getDebug()) {
                        ORBUtility.dprint(this,
                                "internalGetServant: incarnate failed");
                    }

                    // XXX Does the AOM leak in this case? Yes,
                    // but the problem is hard to fix.  There may be
                    // a number of threads waiting for the state to change
                    // from INCARN to something else, which is VALID or
                    // INVALID, depending on the incarnate result.
                    // The activeObjectMap.get() call above creates an
                    // ActiveObjectMap.Entry if one does not already exist,
                    // and stores it in the keyToEntry map in the AOM.
                    entry.incarnateFailure();
                } else {
                    // here check for unique_id policy, and if the servant
                    // is already registered for a different ID, then throw
                    // OBJ_ADAPTER exception, else activate it. Section 11.3.5.1
                    // 99-10-07.pdf
                    if (isUnique) {
                        // check if the servant already is associated with some id
                        if (activeObjectMap.contains((Servant) servant)) {
                            if (poa.getDebug()) {
                                ORBUtility.dprint(this,
                                        "internalGetServant: servant already assigned to ID");
                            }

                            entry.incarnateFailure();
                            throw poa.invocationWrapper().poaServantNotUnique();
                        }
                    }

                    if (poa.getDebug()) {
                        ORBUtility.dprint(this,
                                "internalGetServant: incarnate complete");
                    }

                    entry.incarnateComplete();
                    activateServant(key, entry, (Servant) servant);
                }
            }

            return servant;
        } finally {
            if (poa.getDebug()) {
                ORBUtility.dprint(this,
                        "Exiting POAPolicyMediatorImpl_R_USM.internalGetServant " +
                                "for poa " + poa);
            }
        }
    }

    public void returnServant() {
        OAInvocationInfo info = orb.peekInvocationInfo();
        byte[] id = info.id();
        ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
        AOMEntry entry = activeObjectMap.get(key);
        entry.exit();
    }

    public void etherealizeAll() {
        if (activator != null) {
            Set keySet = activeObjectMap.keySet();

            // Copy the elements in the set to an array to avoid
            // changes in the set due to concurrent modification
            ActiveObjectMap.Key[] keys =
                    (ActiveObjectMap.Key[]) keySet.toArray(
                            new ActiveObjectMap.Key[keySet.size()]);

            for (int ctr = 0; ctr < keySet.size(); ctr++) {
                ActiveObjectMap.Key key = keys[ctr];
                AOMEntry entry = activeObjectMap.get(key);
                Servant servant = activeObjectMap.getServant(entry);
                if (servant != null) {
                    boolean remainingActivations =
                            activeObjectMap.hasMultipleIDs(entry);

                    // Here we etherealize in the thread that called this
                    // method, rather than etherealizing in a new thread
                    // as in the deactivate case.  We still inform the
                    // entry state machine so that only one thread at a
                    // time can call the etherealize method.
                    entry.startEtherealize(null);
                    try {
                        poa.unlock();
                        try {
                            activator.etherealize(key.id, poa, servant, true,
                                    remainingActivations);
                        } catch (Exception exc) {
                            // ignore all exceptions
                        }
                    } finally {
                        poa.lock();
                        entry.etherealizeComplete();
                    }
                }
            }
        }
    }

    public ServantManager getServantManager() throws WrongPolicy {
        return activator;
    }

    public void setServantManager(
            ServantManager servantManager) throws WrongPolicy {
        if (activator != null)
            throw poa.invocationWrapper().servantManagerAlreadySet();

        if (servantManager instanceof ServantActivator)
            activator = (ServantActivator) servantManager;
        else
            throw poa.invocationWrapper().servantManagerBadType();
    }

    public Servant getDefaultServant() throws NoServant, WrongPolicy {
        throw new WrongPolicy();
    }

    public void setDefaultServant(Servant servant) throws WrongPolicy {
        throw new WrongPolicy();
    }

    class Etherealizer extends Thread {
        private POAPolicyMediatorImpl_R_USM mediator;
        private ActiveObjectMap.Key key;
        private AOMEntry entry;
        private Servant servant;
        private boolean debug;

        public Etherealizer(POAPolicyMediatorImpl_R_USM mediator,
                            ActiveObjectMap.Key key, AOMEntry entry, Servant servant,
                            boolean debug) {
            this.mediator = mediator;
            this.key = key;
            this.entry = entry;
            this.servant = servant;
            this.debug = debug;
        }

        public void run() {
            if (debug) {
                ORBUtility.dprint(this, "Calling Etherealizer.run on key " +
                        key);
            }

            try {
                try {
                    mediator.activator.etherealize(key.id, mediator.poa, servant,
                            false, mediator.activeObjectMap.hasMultipleIDs(entry));
                } catch (Exception exc) {
                    // ignore all exceptions
                }

                try {
                    mediator.poa.lock();

                    entry.etherealizeComplete();
                    mediator.activeObjectMap.remove(key);

                    POAManagerImpl pm = (POAManagerImpl) mediator.poa.the_POAManager();
                    POAFactory factory = pm.getFactory();
                    factory.unregisterPOAForServant(mediator.poa, servant);
                } finally {
                    mediator.poa.unlock();
                }
            } finally {
                if (debug) {
                    ORBUtility.dprint(this, "Exiting Etherealizer.run");
                }
            }
        }
    }

    public void deactivateHelper(ActiveObjectMap.Key key, AOMEntry entry,
                                 Servant servant) throws ObjectNotActive, WrongPolicy {
        if (activator == null)
            throw poa.invocationWrapper().poaNoServantManager();

        Etherealizer eth = new Etherealizer(this, key, entry, servant, poa.getDebug());
        entry.startEtherealize(eth);
    }

    public Servant idToServant(byte[] id)
            throws WrongPolicy, ObjectNotActive {
        ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
        AOMEntry entry = activeObjectMap.get(key);

        Servant servant = activeObjectMap.getServant(entry);
        if (servant != null)
            return servant;
        else
            throw new ObjectNotActive();
    }
}
